/*
 *  mih_protocol.c
 *  MIHF
 *
 *  Created by injae song on 11. 3. 5..
 *  Copyright 2011 KyungHee Univ. All rights reserved.
 *
 */

#include "mih_protocol.h"

void init_mih_protocol(mihf *mih) {
	
	warn("Init mih protocol\n");
	
	return;
	
}

void mih_protocol_handle(mih_message* msg, mihf_peer* peer) {
	
	mih_transaction_sm *sm;
	
	sm = mih_msg_get_transaction(msg, peer->mih);
	
	if(sm != NULL) {
		mih_protocol_msg_in(msg, sm, peer);
	}
	
	return;
}
void mihf_peer_send(mihf_peer *peer, mih_message *msg) {
	
	char *mih_frame;
	
	int mih_frame_size;
	
	warn("sending sid=%d, opcode=%d, aid=%d, payloadlength=%d, tid=%d\n", msg->header->sid, msg->header->opcode, msg->header->aid, msg->header->payloadlength, msg->header->mih_tid);
	
	mih_frame = mih_message_serialize(msg);
	
	mih_frame_size = MIH_HEADER_SIZE + msg->header->payloadlength;
	
	mih_net_send(peer->addr, peer->port, mih_frame, mih_frame_size, peer->mih);	
	
	return;	
}
void mih_protocol_msg_in(mih_message* msg, mih_transaction_sm *transaction, mihf_peer *peer) {

	mih_message *msg_out;
	
	int opcode = msg->header->opcode; 
	
	if(transaction->transaction_type == DESTINATION)
	{
		
		warn("msg in for destination fsm\n");
		
		if(transaction->transaction_state == DST_INIT) {
			
			if(opcode == INDICATION) {
				
				transaction->transaction_state = DST_SUCCESS;
				
				transaction->transaction_status = TR_SUCCESS;
				
				mih_protocol_process_indication(msg, transaction);
			
			} 
			else if (opcode == RESPONSE) {
				
				transaction->transaction_state = DST_SUCCESS;
				
				transaction->transaction_status = TR_SUCCESS;
			} 
			else if (opcode == REQUEST) 
			{
				
				transaction->transaction_state = DST_WAIT_RESPONSE_PRM;
				
				mih_protocol_process_request(msg, transaction);
			}
			
			return;	
		} 
		
		if (transaction->transaction_state == DST_WAIT_RESPONSE_PRM)
		{
			msg_out = msg;
			if(msg_out != NULL) 
			{
				if( (!transaction->startACKresponder) || msg_out->header->ackrsp) {
					// FIXME msg_out ?
					mih_protocol_msg_out(msg_out, transaction, peer);
					//	
					if((!transaction->startACKrequestor) || transaction->ACKrequestorstatus== ACK_REQ_SUCCESS) {
						
						transaction->transaction_status = TR_SUCCESS;
						
						transaction->transaction_state = DST_SUCCESS;
						
					}
				}
			}
		}
		else 
		{
			warn("wrong state for msg in\n");
		}
	} 
	else 
	{
		warn("msg in for source fsm\n");
		
		if(transaction->transaction_state == SRC_WAIT_RESPONSE_MSG) {
		
			transaction->response = msg;
			
			transaction->transaction_state = SRC_PROCESS_MSG;
			
			transaction->response_received = TRUE;
			
			transaction->startACKrequestor = (msg->header->ackreq?1:0);
			
			int ismulticast = 0;
			
			if(ismulticast == 1) {
				
				warn("is multicast message\n");
				
				transaction->transaction_state = SRC_WAIT_RESPONSE_MSG;
			} 
			else
			{
				transaction->transaction_state = SRC_SUCCESS;
				
				transaction->transaction_status = TR_SUCCESS;
			}
			
			mih_protocol_process_response(msg, transaction);
		} 
		else 
		{
			warn("wrong state for msg in\n");
		}
	}
	return;
}

void mih_protocol_msg_out(mih_message* msg, mih_transaction_sm *transaction, mihf_peer *peer)
{
	if(transaction->transaction_type == SOURCE) {
		
		warn("msg out for source fsm\n");
		
		if(transaction->transaction_state == SRC_INIT) {
			
			mihf_peer_send(peer, msg);
			
			if(msg->header->opcode == INDICATION) {
				transaction->transaction_state = DST_SUCCESS;
				transaction->transaction_status = TR_SUCCESS;	
			}
			else if (msg->header->opcode == REQUEST)
			{
				transaction->transaction_state = SRC_WAIT_RESPONSE_MSG;
			}
			
		}
	} 
	else if(transaction->transaction_type == DESTINATION) 
	{
		if(transaction->transaction_state == DST_WAIT_RESPONSE_PRM)
		{
			warn("msg out for destination fsm\n");
			
			transaction->transaction_state = DST_SEND_RESPONSE;
			
			transaction->startACKrequestor = (msg->header->ackreq?1:0);
			
			transaction->ACKrequestorstatus = ACK_REQ_ONGOING;	
			
			mihf_peer_send(peer, msg);
		}
	}
}

mih_transaction_sm *mih_msg_get_transaction(mih_message* msg, mihf *mih) {
	
	char *src_mihfid = get_src_mihfid(msg);
	
	char *dst_mihfid = get_dst_mihfid(msg);
	
	int sid = msg->header->sid;
	
	int aid = msg->header->aid;
	
	int opcode = msg->header->opcode;
	
	unsigned int tid = msg->header->mih_tid;	
	
	mih_transaction_sm *pending_sm, *transaction;
	
	mihf_peer *peer;
	
	int transactiontype = -1;
	
	pending_sm = transaction = NULL;
	
	peer = NULL;
	
	warn("received sid=%d, opcode=%d, aid=%d, payloadlength=%d, tid=%d\n", sid, opcode, aid, msg->header->payloadlength, msg->header->mih_tid);
	
	if(strcmp(dst_mihfid, mih->mih_id) != 0) {
		
		warn("MIH message not for this MIHF, discarding\n");
		
		return NULL;
	} 
	
	peer = get_mihf_peer_by_id(src_mihfid, mih);
	
	if(peer == NULL) {	
		warn("No MIHF peer in peer table\n");
		return NULL;
	} 
	
	if( (opcode == REQUEST || opcode == INDICATION) ) {
		
		if(msg->header->ackrsp == 0) {
			
			pending_sm = get_ongoing_dst_transaction_by_tid(tid, mih);
			
			transactiontype = DESTINATION;
		}
		else // need to ack
		{
			warn("error : no support for ackrsp\n");
			
			return NULL;
		}		
	} 
	else if (opcode == RESPONSE) {
		
		if(msg->header->ackrsp == 0) {
			pending_sm = get_ongoing_src_transaction_by_tid(tid, mih);
			transactiontype = SOURCE;
		}
		else {
			warn("no support for ackrsp\n");
			return NULL;
		}
	}
	
	if(pending_sm != NULL) {
		warn("found matching transaction for tid %d\n", tid);
		return pending_sm;
	} 
	else {
		warn("no matching transaction for tid %d\n", tid);
	}
	
	if(transactiontype == DESTINATION) {
		warn("creating destination transaction for tid %d\n", tid);
		transaction = mih_new_dst_transaction(mih);
		transaction->opcode = opcode; 
		transaction->transactionID = tid;	
		transaction->startACKresponder = (msg->header->ackreq?1:0);
		transaction->opcode = opcode; 
		
		switch(opcode) {	
			case REQUEST: 
				transaction->request = msg;
                break;
			case INDICATION:
				transaction->indication = msg;
			default:
                break;
		}
		
		peer->dst_transaction_fsm = transaction;
		
		list_ins_next(&mih->PendingTransaction_table, list_tail(&mih->PendingTransaction_table), (void*) transaction);
		
		return transaction;
	} 
	else if(transactiontype  == SOURCE) {
		warn("NOT creating pending source transaction for tid %d\n", tid);
	}
	
	return transaction;
}

mihf_peer *get_mihf_peer_by_id(char *mihfid, mihf *mih) {

	mihf_peer* _peer;
	int found = 0;
	
	ListElmt *temp;
	
	for ( temp = list_head(&mih->Peer_table); temp != NULL; temp=list_next(temp)) {
		
		_peer = (mihf_peer*) temp->data;
		
		if(strcmp(mihfid, _peer->mih_id) == 0) {
			found = 1;
			break;
		}
	}
	

	if(found == 1) {
		return _peer;
	} else {
		return NULL;
	}	
}

mih_transaction_sm* get_ongoing_src_transaction_by_tid(unsigned int tid, mihf *mih) {
	mih_transaction_sm* sm;
	sm = get_ongoing_transaction_by_tid(tid, mih);
	if( sm != NULL) {
		if(sm->transaction_type == SOURCE) {
			return sm;
		} else {
			return NULL;
		}
	}
	return sm;
}

mih_transaction_sm* get_ongoing_dst_transaction_by_tid(unsigned int tid, mihf *mih) {
	mih_transaction_sm* sm;
	sm = get_ongoing_transaction_by_tid(tid, mih);
	if( sm != NULL) {
		if(sm->transaction_type == DESTINATION) {
			return sm;
		} else {
			return NULL;
		}
	}
	return sm;
}

mih_transaction_sm *get_ongoing_transaction_by_tid(unsigned int tid, mihf *mih) {
	
	mih_transaction_sm* _sm;
	
	int found = 0;
	
	ListElmt *temp;
	
	for ( temp = list_head(&mih->PendingTransaction_table); temp != NULL; temp=list_next(temp)) {
		
		_sm = (mih_transaction_sm*) temp->data;
		
		if( (tid == _sm->transactionID) && (_sm->transaction_status == TR_ONGOING)) {
			
			found = 1;
			
			break;
		}
	}
	
	if(found == 1) {
		return _sm;
	} 
	else 
	{
		return NULL;
	}	
}

mih_transaction_sm *mih_new_src_transaction(mihf *mih) {
	
	mih_transaction_sm *transaction;
	
	warn("creating new source transaction\n");
	
	transaction  = malloc(sizeof(mih_transaction_sm));
	
	transaction->transaction_type = SOURCE;
	
	transaction->transaction_state = SRC_INIT; 	
	
	transaction->transaction_status = TR_ONGOING;
	
	transaction->response_received = FALSE;
	
	
	return transaction;
}

mih_transaction_sm *mih_new_dst_transaction(mihf *mih) {
	
	mih_transaction_sm *transaction;
	
	warn("creating new destination transaction\n");
	
	transaction  = malloc(sizeof(mih_transaction_sm));

	transaction->transaction_type = DESTINATION;
	
	transaction->transaction_state = DST_INIT; 	
	
	transaction->transaction_status = TR_ONGOING;
	
	transaction->response_received = FALSE;
	
	return transaction;
}

unsigned int transaction_id_pick(mihf *mih) {

	int found = 0;
	unsigned int tid = 0;
	
    mih_transaction_sm *_sm;
	
	for(;;) {	
		
        found = 0;
		
        ListElmt *temp;
        
        RAND_pseudo_bytes((unsigned char *) &tid, 2);
		
        tid = tid & 0x0fff;
		
        
        for( temp = list_head(&mih->PendingTransaction_table); temp !=NULL ; temp = list_next(temp))
        {
            _sm =(mih_transaction_sm*) list_data(temp);
			
            if( _sm->transactionID == tid) 
            {
                found = 1;
                
                break;
			}
		}
        
		if(found == 1)
        {    
			warn("TID already in use\n");
		}
        else //Success!
        {
			return tid;
		}	
	}
}

void mih_protocol_transmit(mih_message* msg, void (*cb) (int, mih_message*, void*), void *cb_arg, mihf *mih ) {
	
    mih_transaction_sm *transaction, *src_fsm;
	
    mihf_peer *peer;
    
	char *dst_mihfid = get_dst_mihfid(msg);
	int opcode = msg->header->opcode; 
    
	peer = get_mihf_peer_by_id(dst_mihfid, mih);
	
    if(!peer) 
    {
		warn("Could not find any peer\n");
		
        return;
	}
	
	src_fsm = peer->src_transaction_fsm;
	
	if( (src_fsm == NULL) || (src_fsm != NULL && src_fsm->transaction_state != TR_ONGOING) ) {
		
		transaction = mih_new_src_transaction(mih);
		transaction->cb = cb;
		transaction->cb_arg = cb_arg;
		transaction->opcode = opcode; 
		transaction->transactionID = transaction_id_pick(mih);	
		transaction->startACKrequestor = 0;
        
		msg->header->mih_tid = transaction->transactionID;
        
		switch(opcode) {	
			case REQUEST: 
                transaction->request = msg;
                break;
            case RESPONSE:	
                transaction->response = msg;
                break;
            case INDICATION:
                transaction->indication = msg;
            default:
                break;
		}
		transaction->peer = (void*)peer;
		
        peer->src_transaction_fsm = transaction;
        
        list_ins_next(&mih->PendingTransaction_table, list_tail(&mih->PendingTransaction_table), (void*)transaction);
		
        mih_protocol_msg_out(msg, transaction, peer);
	} 
    else 
    {
		warn("Ongoing source transaction with peer\n");
	} 
    
	return;	
	
}


void mih_protocol_process_response(mih_message* msg, mih_transaction_sm *transaction) {
	warn("mih_protocol_process_response\n");
	//if successful
	if(transaction->transaction_status == TR_SUCCESS) {
		if(transaction->cb != NULL)
			transaction->cb(TR_SUCCESS, msg, transaction->cb_arg);
		
	}
}

void mih_protocol_process_indication(mih_message* msg, mih_transaction_sm *sm) {
	
	warn("mih_protocol_process_indication\n");
	
	switch(msg->header->sid) {
		case SERVICE_MANAGEMENT:
			//	mih_service_management_process_request(msg, sm);
			break;
		case EVENT_SERVICE:
			mih_event_service_process_indication(msg, sm);
			break;
		case COMMAND_SERVICE:
			//	mih_command_service_process_request(msg, sm);
			break;
		case INFORMATION_SERVICE:
			//	mih_information_service_process_request(msg, sm);
			break;
		default:
			break;
	}
}

void mih_protocol_process_request(mih_message* msg, mih_transaction_sm *sm) {
	
	switch(msg->header->sid) {
		case SERVICE_MANAGEMENT:
			//mih_service_management_process_request(msg, sm);
			break;
		case EVENT_SERVICE:
			//	mih_event_service_process_request(msg, sm);
			break;
		case COMMAND_SERVICE:
			//mih_command_service_process_request(msg, sm);
			break;
		case INFORMATION_SERVICE:
			//mih_information_service_process_request(msg, sm);
			break;
		default:
			break;
	}
}
